﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Timers;


#region Port Description
/*
 *   InputLink:  Connection between PLC controller and this program
 *   OutputLink: Connection between this program and Pipe Printer - ZJET303
 * 
 *   ----------------------------------------------------------------------
 *   ZJET303 Interface:
 *   Start Bit: 1
 *   Stop Bit : 1
 *   Data Length: 8
 *   Baud Rate: 4800, 9600
 *   Parity: not present
 * 
 *   PIN:
 *   1  Not used (square pin)
 *   2  RX232  (RS232)
 *   3  TX232  (RS232)
 *   4  + 5Vdc
 *   5  GND
 *   6  TX –  (RS422)
 *   7  TX  +  (RS422)
 *   8  RX –  (RS422)   RX/TX – (RS845)
 *   9  RX  +  (RS422)   RX/TX  + (RS485)
 * 
 * */
#endregion

#region ZETA2 Protocol
/**
 * 
 *   Packet Structure:
 *   | 0  1  2  3  4  5 ......( Max:240 Bytes) ..............|
 *   | $ MA SA X1 X2  DATA ... ....... ...... ....C1  C2  CR |
 * 
 *   $:  Start Byte ( ASCII 36)
 *   MA: Master Address
 *   SA: Slave Address
 *   X1: Command Byte1
 *   X2: Command Byte2
 *   DATA: Data Bytes, up to 240 bytes
 *   C1: Checksum Byte1
 *   C2: Checksum Byte2
 *       Checksum can be replaced by 2 spaces ( ASCII 32 )
 *   CR: End Byte ( ASCII 13 )
 * 
 *   -----------------------------------------------------------
 *   X1X2:
 *   WT: Write Text
 *   RT: Read Text
 *   WP: Write Parameters
 *   WC: Write Counter
 *   RR: Read Report
 *   WG: Write Graph
 *   RG: Read Graph
 *   RL: Read version
 *   RK: Read Clock
 *   WK: Write Clock
 *   RD: Read Data
 *   WD:
 *   RO:
 *   WO:
 *   RA: Read Alert
 *   EM: Erase Memory
 *   WM: Write Memory
 *   RM: Read Mark
 *   SL: Set Line
 * 
 * */
#endregion

namespace PipePrinter
{
    public enum PortStates
    {
        NOT_CONNECTED,
        OPENING_LINK,
        CLOSING_LINK,
        WAIT_FOR_EVENT,
    };

    public enum PEvents
    {
        OPEN_LINK,
        CLOSE_LINK,

        INPUT_LINK_UP,
        INPUT_LINK_DOWN,

        OUTPUT_LINK_UP,
        OUTPUT_LINK_DOWN,

        // for input link
        INPUT_LINK_BYTES_RCVD,
        INPUT_LINK_BYTES_SENT,

        // for output link
        OUTPUT_LINK_BYTES_RCVD,
        OUTPUT_LINK_BYTES_SENT,

        // for task
        TASK_COUNT_FIRED,
        TASK_TIMER_FIRED,
        TASK_SEND,

        PROT_ERROR,
    };

    public class ZETA2Packet
    {
        public const byte START = 36;
        public const byte STOP = 13;
    }

    public delegate void TaskQueueChanged();

    public class PrinterProtocol
    {
        // links and link state
        private InputLink inputLink;
        private OutputLink outputLink;
        private PortStates iPortState;
        private PortStates oPortState;

        // input link receive buffer
        protected byte[] riBuf;
        protected int riBufHead;
        protected int riBufTail;
        protected int riBufSize = 4096;

        // output link receive buffer
        protected byte[] roBuf;
        protected int roBufHead;
        protected int roBufTail;
        protected int roBufSize = 4096;

        // task pool
        // save the tasks from input link to output link
        protected Queue<string> taskQueue;
        protected int taskCount;
        protected System.Timers.Timer taskTimer;
        protected DelayConfig taskDelayConfig;

        // events
        protected bool exitThread = false;
        protected Thread intThread;
        protected AutoResetEvent autoEvent;
        protected Queue<PEvents> eventQueue;
        protected PEvents[] eventBuf;
        protected int eventBufHead;
        protected int eventBufTail;

        // Tracer
        protected TraceHelper Tracer;


        public PrinterProtocol()
        {
            Tracer = new TraceHelper();

            inputLink = InputLink.getInstance();
            outputLink = OutputLink.getInstance();

            iPortState = PortStates.NOT_CONNECTED;
            oPortState = PortStates.NOT_CONNECTED;
        }

        public void Configure()
        {
            taskDelayConfig = SysConfig.getInstance().Delay;
            taskTimer.Interval = int.Parse(taskDelayConfig.DelayMsValue);
            if (taskDelayConfig.Type == DelayType.ByTime)
                taskTimer.Enabled = true;
            else
                taskTimer.Enabled = false;

            SysConfig.getInstance().InputPort.ConfigLink(inputLink);
            SysConfig.getInstance().OutputPort.ConfigLink(outputLink);
        }

        public void Initialize()
        {
            riBuf = new byte[riBufSize];
            riBufHead = 0;
            riBufTail = 0;

            roBuf = new byte[roBufSize];
            roBufHead = 0;
            roBufTail = 0;

            taskQueue = new Queue<string>();
            taskTimer = new System.Timers.Timer();
            taskTimer.Elapsed += new ElapsedEventHandler(OnTaskTimerEvent);
            taskCount = 0;

            eventQueue = new Queue<PEvents>(100);
            eventBuf = new PEvents[1000];
            eventBufHead = 0;
            eventBufTail = 0;
            autoEvent = new AutoResetEvent(false);

            Configure();

            ThreadStart threadDelegate = new ThreadStart(getEvent);
            intThread = new Thread(threadDelegate);
            intThread.Start();

        }

        protected void ResetProtocol()
        {
            taskTimer.Enabled = false;
            iPortState = PortStates.NOT_CONNECTED;
            oPortState = PortStates.NOT_CONNECTED;

            taskQueue.Clear();
            riBufHead = 0;
            riBufTail = 0;
            roBufHead = 0;
            roBufTail = 0;
            eventQueue.Clear();
            eventBufHead = 0;
            eventBufTail = 0;
        }

        public void SetTraceCallback(traceCallBack trCB)
        {
            inputLink.SetTraceCallback(trCB);
            outputLink.SetTraceCallback(trCB);
            Tracer.SetTraceCallback(trCB);
        }

        public void OpenLink()
        {
            PostEvent(PEvents.OPEN_LINK);
        }

        public void CloseLink()
        {
            PostEvent(PEvents.CLOSE_LINK);
        }

        public void Exit()
        {
            this.exitThread = true;
        }

        public void SendTask()
        {
            PostEvent(PEvents.TASK_SEND);
        }

        private void PostEvent(PEvents evt)
        {
            Tracer.traceLog("== POST EVENT: " + evt.ToString());

            lock (this)
            {
                // eventQueue.Enqueue(evt);
                eventBuf[eventBufHead++] = evt;
                if (eventBufHead == 1000)
                    eventBufHead = 0;
            }
            autoEvent.Set();
            // runStateMachine(evt);
        }

        private void getEvent()
        {
            PEvents msg;

            while (!exitThread)
            {
                autoEvent.WaitOne(1000, false);

                if (exitThread == true)
                    return;
                //if (eventQueue.Count > 0)
                if (eventBufHead != eventBufTail)
                {
                    // msg = eventQueue.Dequeue();
                    msg = eventBuf[eventBufTail++];
                    if (eventBufTail == 1000)
                        eventBufTail = 0;

                    runStateMachine(msg);
                }
                //if (eventQueue.Count > 0)
                if (eventBufHead != eventBufTail)
                    autoEvent.Set();
            }
        }

        private void OnTaskTimerEvent(object source, ElapsedEventArgs e)
        {
            PostEvent(PEvents.TASK_TIMER_FIRED);
        }

        // the link layer calls this function to add bytes to our rcv queue
        protected void inputLinkBytesRcvdCB(byte data)
        {
            //rcvBuffer.Enqueue(data);
            riBuf[riBufHead++] = data;
            if (riBufHead == riBufSize)
                riBufHead = 0;
        }

        // the link layer calls this function to add bytes to our rcv queue
        protected void outputLinkBytesRcvdCB(byte data)
        {
            roBuf[roBufHead++] = data;
            if (roBufHead == roBufSize)
                roBufHead = 0;
        }

        // called after received bytes from input link
        protected void handleInputBytesRecvd()
        {
            string tmp = "";
            byte[] tmpbuf = new byte[riBufSize];
            int count = 0;

            while (roBufHead != roBufTail)
            {
                tmpbuf[count++] = riBuf[riBufTail++];
                if (riBufTail == riBufSize)
                    riBufTail = 0;
            }
            if (count > 0)
                outputLink.write(tmpbuf, 0, count);

            for (int i = 0; i < count; i++)
                tmp += (char)riBuf[i];
            Tracer.traceLog(tmp);

            // parse packets and get task
            // put task -> task queue
            // trigger new task event - for displaying
            // update taskCount if it is a task, 
            // if taskCount>setting, postevent(TASK_COUNT_FIRED), update taskCount
        }

        protected void handleInputPacketSend()
        {
            // add something if needed
        }

        // called after received bytes from output link
        protected void handleOutputBytesRecvd()
        {
            byte[] tmpbuf = new byte[roBufSize];
            int count = 0;

            while (roBufHead != roBufTail)
            {
                tmpbuf[count++] = roBuf[roBufTail++];
                if (roBufTail == roBufSize)
                    roBufTail = 0;
            }
            if (count > 0)
                inputLink.write(tmpbuf, 0, count);

            // add something if needed
        }

        protected void handleOutputSendTask()
        {
            // get task from task queue
            // send task to output link
            // update taskCount
        }

        // called from the context of the link driver
        protected void linkLayerCB(linkType type, linkMsgs message)
        {
            switch (message)
            {
                case linkMsgs.LINK_UP:
                    {
                        if (type == linkType.INPUT_LINK)
                            PostEvent(PEvents.INPUT_LINK_UP);
                        else
                            PostEvent(PEvents.OUTPUT_LINK_UP);
                    }
                    break;

                case linkMsgs.LINK_DOWN:
                    {
                        if (type == linkType.INPUT_LINK)
                            PostEvent(PEvents.INPUT_LINK_DOWN);
                        else
                            PostEvent(PEvents.OUTPUT_LINK_DOWN);
                    }
                    break;

                case linkMsgs.BYTES_RECEIVED:
                    {
                        if (type == linkType.INPUT_LINK)
                            PostEvent(PEvents.INPUT_LINK_BYTES_RCVD);
                        else
                            PostEvent(PEvents.OUTPUT_LINK_BYTES_RCVD);
                    }
                    break;
                case linkMsgs.BYTES_SENT:
                    {
                        if (type == linkType.INPUT_LINK)
                            PostEvent(PEvents.INPUT_LINK_BYTES_SENT);
                        else
                            PostEvent(PEvents.OUTPUT_LINK_BYTES_SENT);
                    }
                    break;
            }
        }

        protected virtual void runStateMachine(PEvents Event)
        {
            switch (Event)
            {
                case PEvents.OPEN_LINK:
                    {
                        if (iPortState == PortStates.NOT_CONNECTED)
                        {
                            inputLink.SetEventCallback(linkLayerCB);
                            inputLink.SetBufferCallback(inputLinkBytesRcvdCB);
                            iPortState = PortStates.OPENING_LINK;
                            inputLink.openLink();
                        }

                        if (oPortState == PortStates.NOT_CONNECTED)
                        {
                            outputLink.SetEventCallback(linkLayerCB);
                            outputLink.SetBufferCallback(outputLinkBytesRcvdCB);
                            oPortState = PortStates.OPENING_LINK;
                            outputLink.openLink();
                        }
                    }
                    break;

                case PEvents.CLOSE_LINK:
                    {
                        if (iPortState == PortStates.WAIT_FOR_EVENT || iPortState == PortStates.OPENING_LINK)
                        {
                            if (inputLink.IsOpen())
                                inputLink.closeLink();

                            iPortState = PortStates.NOT_CONNECTED;
                        }

                        if (oPortState == PortStates.WAIT_FOR_EVENT || oPortState == PortStates.OPENING_LINK)
                        {
                            if (outputLink.IsOpen())
                                outputLink.closeLink();

                            oPortState = PortStates.NOT_CONNECTED;
                        }
                    }
                    break;

                case PEvents.INPUT_LINK_BYTES_RCVD:
                    handleInputBytesRecvd();
                    break;

                case PEvents.INPUT_LINK_BYTES_SENT:
                    handleInputPacketSend();
                    break;

                case PEvents.OUTPUT_LINK_BYTES_RCVD:
                    handleOutputBytesRecvd();
                    break;

                case PEvents.TASK_TIMER_FIRED:
                    handleOutputSendTask();
                    break;

                case PEvents.TASK_COUNT_FIRED:
                    handleOutputSendTask();
                    break;

                case PEvents.TASK_SEND:
                    handleOutputSendTask();
                    break;
            }

            switch (iPortState)
            { 
                case PortStates.NOT_CONNECTED:
                    break;

                case PortStates.OPENING_LINK:
                    break;

                case PortStates.WAIT_FOR_EVENT:
                    break;

                case PortStates.CLOSING_LINK:
                    break;
            }

            switch (oPortState)
            { 
                case PortStates.NOT_CONNECTED:
                    break;

                case PortStates.OPENING_LINK:
                    break;

                case PortStates.WAIT_FOR_EVENT:
                    break;

                case PortStates.CLOSING_LINK:
                    break;
            }
        }
    }
}
